/* * Copyright 2001-2009 Stephen Colebourne * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.joda.time.convert; import org.joda.time.Chronology; import org.joda.time.DateTime; import org.joda.time.Period; import org.joda.time.ReadWritableInterval; import org.joda.time.ReadWritablePeriod; import org.joda.time.ReadablePartial; import org.joda.time.field.FieldUtils; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; import org.joda.time.format.ISOPeriodFormat; import org.joda.time.format.PeriodFormatter; /** * StringConverter converts from a String to an instant, partial, * duration, period or interval.. * * @author Stephen Colebourne * @author Brian S O'Neill * @since 1.0 */ class StringConverter extends AbstractConverter implements InstantConverter, PartialConverter, DurationConverter, PeriodConverter, IntervalConverter { /** * Singleton instance. */ static final StringConverter INSTANCE = new StringConverter(); /** * Restricted constructor. */ protected StringConverter() { super(); } //----------------------------------------------------------------------- /** * Gets the millis, which is the ISO parsed string value. * * @param object the String to convert, must not be null * @param chrono the chronology to use, non-null result of getChronology * @return the millisecond value * @throws IllegalArgumentException if the value if invalid */ public long getInstantMillis(Object object, Chronology chrono) { String str = (String) object; DateTimeFormatter p = ISODateTimeFormat.dateTimeParser(); return p.withChronology(chrono).parseMillis(str); } /** * Extracts the values of the partial from an object of this converter's type. * This method checks if the parser has a zone, and uses it if present. * This is most useful for parsing local times with UTC. * * @param fieldSource a partial that provides access to the fields. * This partial may be incomplete and only getFieldType(int) should be used * @param object the object to convert * @param chrono the chronology to use, which is the non-null result of getChronology() * @param parser the parser to use, may be null * @return the array of field values that match the fieldSource, must be non-null valid * @throws ClassCastException if the object is invalid * @throws IllegalArgumentException if the value if invalid * @since 1.3 */ public int[] getPartialValues(ReadablePartial fieldSource, Object object, Chronology chrono, DateTimeFormatter parser) { if (parser.getZone() != null) { chrono = chrono.withZone(parser.getZone()); } long millis = parser.withChronology(chrono).parseMillis((String) object); return chrono.get(fieldSource, millis); } //----------------------------------------------------------------------- /** * Gets the duration of the string using the standard type. * This matches the toString() method of ReadableDuration. * * @param object the String to convert, must not be null * @throws ClassCastException if the object is invalid */ public long getDurationMillis(Object object) { // parse here because duration could be bigger than the int supported // by the period parser String original = (String) object; String str = original; int len = str.length(); if (len >= 4 && (str.charAt(0) == 'P' || str.charAt(0) == 'p') && (str.charAt(1) == 'T' || str.charAt(1) == 't') && (str.charAt(len - 1) == 'S' || str.charAt(len - 1) == 's')) { // ok } else { throw new IllegalArgumentException("Invalid format: \"" + original + '"'); } str = str.substring(2, len - 1); int dot = -1; boolean negative = false; for (int i = 0; i < str.length(); i++) { if (str.charAt(i) >= '0' && str.charAt(i) <= '9') { // ok } else if (i == 0 && str.charAt(0) == '-') { // ok negative = true; } else if (i > (negative ? 1 : 0) && str.charAt(i) == '.' && dot == -1) { // ok dot = i; } else { throw new IllegalArgumentException("Invalid format: \"" + original + '"'); } } long millis = 0, seconds = 0; int firstDigit = negative ? 1 : 0; if (dot > 0) { seconds = Long.parseLong(str.substring(firstDigit, dot)); str = str.substring(dot + 1); if (str.length() != 3) { str = (str + "000").substring(0, 3); } millis = Integer.parseInt(str); } else if (negative) { seconds = Long.parseLong(str.substring(firstDigit, str.length())); } else { seconds = Long.parseLong(str); } if (negative) { return FieldUtils.safeAdd(FieldUtils.safeMultiply(-seconds, 1000), -millis); } else { return FieldUtils.safeAdd(FieldUtils.safeMultiply(seconds, 1000), millis); } } //----------------------------------------------------------------------- /** * Extracts duration values from an object of this converter's type, and * sets them into the given ReadWritableDuration. * * @param period period to get modified * @param object the String to convert, must not be null * @param chrono the chronology to use * @return the millisecond duration * @throws ClassCastException if the object is invalid */ public void setInto(ReadWritablePeriod period, Object object, Chronology chrono) { String str = (String) object; PeriodFormatter parser = ISOPeriodFormat.standard(); period.clear(); int pos = parser.parseInto(period, str, 0); if (pos < str.length()) { if (pos < 0) { // Parse again to get a better exception thrown. parser.withParseType(period.getPeriodType()).parseMutablePeriod(str); } throw new IllegalArgumentException("Invalid format: \"" + str + '"'); } } //----------------------------------------------------------------------- /** * Sets the value of the mutable interval from the string. * * @param writableInterval the interval to set * @param object the String to convert, must not be null * @param chrono the chronology to use, may be null */ public void setInto(ReadWritableInterval writableInterval, Object object, Chronology chrono) { String str = (String) object; int separator = str.indexOf('/'); if (separator < 0) { throw new IllegalArgumentException("Format requires a '/' separator: " + str); } String leftStr = str.substring(0, separator); if (leftStr.length() <= 0) { throw new IllegalArgumentException("Format invalid: " + str); } String rightStr = str.substring(separator + 1); if (rightStr.length() <= 0) { throw new IllegalArgumentException("Format invalid: " + str); } DateTimeFormatter dateTimeParser = ISODateTimeFormat.dateTimeParser(); dateTimeParser = dateTimeParser.withChronology(chrono); PeriodFormatter periodParser = ISOPeriodFormat.standard(); long startInstant = 0, endInstant = 0; Period period = null; Chronology parsedChrono = null; // before slash char c = leftStr.charAt(0); if (c == 'P' || c == 'p') { period = periodParser.withParseType(getPeriodType(leftStr)).parsePeriod(leftStr); } else { DateTime start = dateTimeParser.parseDateTime(leftStr); startInstant = start.getMillis(); parsedChrono = start.getChronology(); } // after slash c = rightStr.charAt(0); if (c == 'P' || c == 'p') { if (period != null) { throw new IllegalArgumentException("Interval composed of two durations: " + str); } period = periodParser.withParseType(getPeriodType(rightStr)).parsePeriod(rightStr); chrono = (chrono != null ? chrono : parsedChrono); endInstant = chrono.add(period, startInstant, 1); } else { DateTime end = dateTimeParser.parseDateTime(rightStr); endInstant = end.getMillis(); parsedChrono = (parsedChrono != null ? parsedChrono : end.getChronology()); chrono = (chrono != null ? chrono : parsedChrono); if (period != null) { startInstant = chrono.add(period, endInstant, -1); } } writableInterval.setInterval(startInstant, endInstant); writableInterval.setChronology(chrono); } //----------------------------------------------------------------------- /** * Returns String.class. * * @return String.class */ public Class<?> getSupportedType() { return String.class; } }